﻿Namespace Validation
    ''' <summary>
    ''' Provides a Submit command to any control it is attached to. The command will be disabled
    ''' when the control has ValidationErrors.
    ''' </summary>
    ''' <remarks>This class implements the Sticky Command pattern. It will add a BindingGroup to
    ''' the control if it doesn't have one already.</remarks>
    Public Class Submit
        Inherits DependencyObject

        ''' <summary>
        ''' The RoutedCommand which should be the submit command. Use any RoutedCommand instance
        ''' to connect the submit behaviour to it.
        ''' </summary>
        ''' <remarks></remarks>
        Public Shared ReadOnly CommandProperty As DependencyProperty = _
                DependencyProperty.RegisterAttached("Command", _
                GetType(RoutedCommand), GetType(Submit), _
                New PropertyMetadata(AddressOf OnCommandChanged))
        Public Shared Function GetCommand(ByVal d As DependencyObject) _
                As RoutedCommand
            Return d.GetValue(CommandProperty)
        End Function
        Public Shared Sub SetCommand(ByVal d As DependencyObject, _
                ByVal value As RoutedCommand)
            d.SetValue(CommandProperty, value)
        End Sub
        Private Shared Sub OnCommandChanged(ByVal d As DependencyObject, _
            ByVal e As DependencyPropertyChangedEventArgs)
            Dim f As FrameworkElement = TryCast(d, FrameworkElement)
            If f IsNot Nothing Then
                If e.OldValue IsNot Nothing Then
                    Detach(f, e.OldValue)
                End If
                If e.NewValue IsNot Nothing Then
                    Attach(f, e.NewValue)
                End If
            End If
        End Sub

        ''' <summary>
        ''' A default KeyGesture that should be connected to the submit command.
        ''' </summary>
        ''' <remarks></remarks>
        Public Shared ReadOnly KeyGestureProperty As DependencyProperty = _
        DependencyProperty.RegisterAttached("KeyGesture", _
                            GetType(KeyGesture), GetType(Submit), _
                            New PropertyMetadata(AddressOf OnKeyGestureChanged))
        Public Shared Function GetKeyGesture(ByVal d As DependencyObject) _
                As KeyGesture
            Return d.GetValue(KeyGestureProperty)
        End Function
        Public Shared Sub SetKeyGesture(ByVal d As DependencyObject, _
                                        ByVal value As KeyGesture)
            d.SetValue(KeyGestureProperty, value)
        End Sub
        Private Shared Sub OnKeyGestureChanged(ByVal d As DependencyObject, _
                ByVal e As DependencyPropertyChangedEventArgs)
            Dim f As FrameworkElement = TryCast(d, FrameworkElement)
            If f IsNot Nothing Then
                If e.OldValue IsNot Nothing Then
                    DetachKeyGesture(f)
                End If
                If e.NewValue IsNot Nothing Then
                    AttachKeyGesture(f, GetCommand(f), e.NewValue)
                End If
            End If
        End Sub

        Private Shared Sub Attach(ByVal d As FrameworkElement, _
                                  ByVal command As RoutedCommand)
            d.CommandBindings.Add(New SubmitCommandBinding(command))
            Dim gesture As KeyGesture = GetKeyGesture(d)
            AttachKeyGesture(d, command, gesture)
            If d.BindingGroup Is Nothing Then
                d.BindingGroup = New BindingGroup
            End If
        End Sub

        Private Shared Sub AttachKeyGesture(ByVal d As FrameworkElement, _
                                            ByVal command As RoutedCommand, _
                                            ByVal gesture As KeyGesture)
            ' Can only attach a KeyGesture when both the gesture and the command have been set.
            If command IsNot Nothing And gesture IsNot Nothing Then
                d.InputBindings.Add(New SubmitKeyBinding(command, gesture))
            End If
        End Sub
        Private Shared Sub Detach(ByVal base As FrameworkElement, _
                                  ByVal command As RoutedCommand)
            ' Use the type of the CommandBinding to find the sticky binding without storing a reference to it
            For Each b As SubmitCommandBinding In _
                    base.CommandBindings.OfType(Of SubmitCommandBinding)()
                base.CommandBindings.Remove(b)
            Next
            DetachKeyGesture(base)
        End Sub
        Private Shared Sub DetachKeyGesture(ByVal base As FrameworkElement)
            ' Use the type of the InputBinding to find the sticky binding without storing a reference to it
            For Each i As SubmitKeyBinding In _
                    base.InputBindings.OfType(Of SubmitKeyBinding)()
                base.InputBindings.Remove(i)
            Next
        End Sub


        Private Shared Sub OnCanExecuteSubmit(ByVal sender As Object, _
                ByVal e As CanExecuteRoutedEventArgs)

            Dim result As Boolean = False
            Dim target As FrameworkElement = TryCast(sender, FrameworkElement)
            If target IsNot Nothing Then
                ' Separate event specific code from domain specific code...
                result = CanExecuteSubmit(target, e.Parameter)
            End If
            e.CanExecute = result
            e.Handled = True
        End Sub

        ''' <summary>
        ''' Contains the logic that checks whether the command can be executed
        ''' </summary>
        ''' <param name="target"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Private Shared Function CanExecuteSubmit(ByVal target As FrameworkElement, ByVal parameter As Object) _
                As Boolean
            Dim result As Boolean = False
            If target IsNot Nothing Then
                result = (target.BindingGroup IsNot Nothing AndAlso Not _
                          System.Windows.Controls.Validation.GetHasError(target))
            End If
            Return result
        End Function

        Private Shared Sub OnExecutedSubmit(ByVal sender As Object, _
                                            ByVal e As ExecutedRoutedEventArgs)
            Dim target As FrameworkElement = TryCast(sender, FrameworkElement)
            If target IsNot Nothing Then
                If CanExecuteSubmit(target, e.Parameter) Then
                    ExecuteSubmit(target, e.Parameter)
                End If
                e.Handled = True
            End If
        End Sub


        ''' <summary>
        ''' Contains the logic that executes the command...
        ''' </summary>
        ''' <param name="target"></param>
        ''' <remarks></remarks>
        Private Shared Sub ExecuteSubmit(ByVal target As FrameworkElement, ByVal parameter As Object)
            target.BindingGroup.UpdateSources()
        End Sub

        ''' <summary>
        ''' Dummy class in order to mark CommandBindings that have been set up through this sticky
        ''' command with their type.
        ''' </summary>
        ''' <remarks></remarks>
        Private Class SubmitCommandBinding
            Inherits CommandBinding
            Public Sub New(ByVal command As RoutedCommand)
                MyBase.New(command, AddressOf OnExecutedSubmit, AddressOf OnCanExecuteSubmit)
            End Sub
        End Class

        ''' <summary>
        ''' Dummy class in order to mark InputBindings that have been set up through this sticky
        ''' command with their type.
        ''' </summary>
        ''' <remarks></remarks>
        Private Class SubmitKeyBinding
            Inherits InputBinding
            Public Sub New(ByVal command As RoutedCommand, ByVal gesture As KeyGesture)
                MyBase.New(command, gesture)
            End Sub
        End Class
    End Class
End Namespace
